Skip to content

Project Un-Slop pt 1: Rename slop gems, improve their metrics, and consume them to reduce complexity. Also, remove legacy %T.#63

Merged
cuzzo merged 106 commits into
masterfrom
decomplex
May 19, 2026
Merged

Project Un-Slop pt 1: Rename slop gems, improve their metrics, and consume them to reduce complexity. Also, remove legacy %T.#63
cuzzo merged 106 commits into
masterfrom
decomplex

Conversation

@cuzzo

@cuzzo cuzzo commented May 15, 2026

Copy link
Copy Markdown
Owner

Evolves triage from "group dark arms by method" to "classify each dark arm by which testing modality can reach it". Four buckets:

fuzz_axis valid program, unseen shape (case-on-AST, &&/||,
live if/while body) -> one fuzz axis covers a
family + a mutant
negative_spec the arm raises/diagnoses -> invalid-program only;
fuzz cannot reach it by construction
ffi_integration extern/require/module boundary -> needs a real
external artifact a fuzzer can't synthesize
accept_defensive narrow inert residue (synthetic else, empty, nil)
-> annotate + accept; human-confirmed, never
auto-accepts a reachable arm

Classification is AST-structural, NOT a regex over the arm line (the rejected fake-value grep): the SimpleCov parent tuple gives the decision kind, and the arm's (line,col) span is matched to an AST node whose subtree is inspected for raise/FFI. Two PER-PROJECT LEXICON constants (FFI boundary methods, diagnostic message names) are the only project-specific knobs -- the algorithm generalizes, swap the lexicon per codebase.

Result over the 3 lowering files: fuzz_axis 590, accept_defensive 296, ffi_integration 53, negative_spec 16. This is the work plan: not one fuzz test, not tons of unit tests, not an integration suite -- overwhelmingly fuzz axes, a bounded FFI .cht set, a tiny negative-spec set, a human-confirmed accept residue.

Lives entirely in the coverage tool; decomplex untouched and stays static/zero-runtime (boundary preserved).

@github-actions

github-actions Bot commented May 15, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchdecomplex
Testbedubuntu-latest

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
Benchmarkleak-build-msMeasure (units) x 1e3leak-countMeasure (units)leak-run-msMeasure (units)
benchmarks/concurrent/01_socket_throughput/bench📈 view plot
⚠️ NO THRESHOLD
6.63 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
1,370.04 units
benchmarks/concurrent/06_dynamic_spawn/bench📈 view plot
⚠️ NO THRESHOLD
5.40 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
3,784.26 units
benchmarks/concurrent/11_parallel_aggregation/bench📈 view plot
⚠️ NO THRESHOLD
5.19 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
7,209.46 units
benchmarks/concurrent/18_atomic_counter/bench📈 view plot
⚠️ NO THRESHOLD
5.19 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
50.69 units
benchmarks/inter-clear/04_concurrent_mvcc_fat_struct/bench📈 view plot
⚠️ NO THRESHOLD
5.31 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
304.09 units
benchmarks/sequential/03_alloc_throughput/bench📈 view plot
⚠️ NO THRESHOLD
5.25 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
11,536.05 units
benchmarks/sequential/13_soa_layout/bench📈 view plot
⚠️ NO THRESHOLD
5.32 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
758.16 units
🐰 View full continuous benchmarking report in Bencher

@codecov-commenter

codecov-commenter commented May 15, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 94.98542% with 86 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.22%. Comparing base (77f8479) to head (8b00156).

Files with missing lines Patch % Lines
src/ast/ast.rb 95.36% 16 Missing ⚠️
src/backends/pipeline_rewriter.rb 88.50% 10 Missing ⚠️
src/mir/mir_lowering.rb 95.65% 9 Missing ⚠️
src/annotator-helpers/pipe_analysis.rb 89.61% 8 Missing ⚠️
src/annotator-helpers/intrinsic_registry.rb 92.13% 7 Missing ⚠️
src/mir/promotion_plan.rb 87.93% 7 Missing ⚠️
src/annotator-helpers/generic_analysis.rb 89.36% 5 Missing ⚠️
src/mir/pre_mir_type_check.rb 85.29% 5 Missing ⚠️
src/mir/concurrency_checks.rb 25.00% 3 Missing ⚠️
src/annotator-helpers/function_analysis.rb 98.00% 2 Missing ⚠️
... and 10 more
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #63      +/-   ##
==========================================
+ Coverage   92.71%   93.22%   +0.51%     
==========================================
  Files         208      183      -25     
  Lines       52716    49473    -3243     
  Branches    12381    10938    -1443     
==========================================
- Hits        48876    46122    -2754     
+ Misses       3840     3351     -489     
Flag Coverage Δ
bc-lower 81.06% <89.67%> (?)
fuzz 47.11% <50.37%> (+1.00%) ⬆️
ruby 91.23% <94.98%> (+0.62%) ⬆️
transpile-tests 82.53% <90.20%> (+0.41%) ⬆️
zig 96.36% <ø> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

github-actions Bot commented May 15, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchdecomplex
Testbedubuntu-latest

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
Benchmarkleak-build-msMeasure (units) x 1e3leak-countMeasure (units)leak-run-msMeasure (units)
benchmarks/concurrent/03_atomic_contention/bench📈 view plot
⚠️ NO THRESHOLD
6.15 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
82.97 units
benchmarks/concurrent/08_pubsub/bench📈 view plot
⚠️ NO THRESHOLD
5.34 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
2,913.91 units
benchmarks/concurrent/13_rwlock_starvation/bench📈 view plot
⚠️ NO THRESHOLD
5.39 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
1,247.61 units
benchmarks/inter-clear/06_concurrent_mvcc_writer_pressure/bench📈 view plot
⚠️ NO THRESHOLD
5.40 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
1,834.29 units
benchmarks/sequential/05_string_builder/bench📈 view plot
⚠️ NO THRESHOLD
5.28 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
28,063.65 units
benchmarks/sequential/10_pool_vs_multiowned/bench📈 view plot
⚠️ NO THRESHOLD
5.21 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
748.92 units
benchmarks/server/01_tcp_kvstore/server📈 view plot
⚠️ NO THRESHOLD
5.38 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
1,002.61 units
🐰 View full continuous benchmarking report in Bencher

@github-actions

github-actions Bot commented May 15, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchdecomplex
Testbedubuntu-latest

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
Benchmarkleak-build-msMeasure (units) x 1e3leak-countMeasure (units)leak-run-msMeasure (units)
benchmarks/concurrent/02_concurrent_search/bench📈 view plot
⚠️ NO THRESHOLD
4.01 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
5.97 units
benchmarks/concurrent/07_stream_merge/bench📈 view plot
⚠️ NO THRESHOLD
4.02 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
31.05 units
benchmarks/concurrent/12_false_sharing/bench📈 view plot
⚠️ NO THRESHOLD
3.93 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
1,002.13 units
benchmarks/concurrent/19_atomic_ptr/bench📈 view plot
⚠️ NO THRESHOLD
3.95 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
91.51 units
benchmarks/inter-clear/05_concurrent_mvcc_pure_read/bench📈 view plot
⚠️ NO THRESHOLD
4.07 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
501.64 units
benchmarks/sequential/04_hashmap/bench📈 view plot
⚠️ NO THRESHOLD
4.02 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
1,689.84 units
benchmarks/sequential/09_frame_vs_heap/bench📈 view plot
⚠️ NO THRESHOLD
3.87 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
1,642.19 units
benchmarks/sequential/14_iterator/bench📈 view plot
⚠️ NO THRESHOLD
3.97 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
370.41 units
🐰 View full continuous benchmarking report in Bencher

@github-actions

github-actions Bot commented May 15, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchdecomplex
Testbedubuntu-latest

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
Benchmarkleak-build-msMeasure (units) x 1e3leak-countMeasure (units)leak-run-msMeasure (units)
benchmarks/concurrent/05_backpressure/bench📈 view plot
⚠️ NO THRESHOLD
5.47 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
1,595.74 units
benchmarks/concurrent/10_shard_vs_locked/bench📈 view plot
⚠️ NO THRESHOLD
5.26 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
60,004.57 units
benchmarks/concurrent/16_observables/bench📈 view plot
⚠️ NO THRESHOLD
5.18 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
96.18 units
benchmarks/inter-clear/03_concurrent_mvcc_vs_rwlock/bench📈 view plot
⚠️ NO THRESHOLD
5.97 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
314.37 units
benchmarks/sequential/07_pointer_chase/bench📈 view plot
⚠️ NO THRESHOLD
5.18 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
515.82 units
benchmarks/sequential/12_weak_ref_graph/bench📈 view plot
⚠️ NO THRESHOLD
5.20 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
258.84 units
benchmarks/server/03_pathological/server📈 view plot
⚠️ NO THRESHOLD
5.38 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
1,002.74 units
🐰 View full continuous benchmarking report in Bencher

@github-actions

github-actions Bot commented May 15, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchdecomplex
Testbedubuntu-latest

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
Benchmarkleak-build-msMeasure (units) x 1e3leak-countMeasure (units)leak-run-msMeasure (units)
benchmarks/concurrent/04_fanout_fanin/bench📈 view plot
⚠️ NO THRESHOLD
5.57 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
3,266.30 units
benchmarks/concurrent/09_kvstore/bench📈 view plot
⚠️ NO THRESHOLD
5.47 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
60,003.90 units
benchmarks/concurrent/14_nested_lock/bench📈 view plot
⚠️ NO THRESHOLD
5.43 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
388.92 units
benchmarks/inter-clear/02_concurrent_fsm_vs_stackful/bench_fsm📈 view plot
⚠️ NO THRESHOLD
5.55 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
154.99 units
benchmarks/inter-clear/02_concurrent_fsm_vs_stackful/bench_stackful📈 view plot
⚠️ NO THRESHOLD
5.29 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
198.60 units
benchmarks/sequential/11_pipeline_overhead/bench📈 view plot
⚠️ NO THRESHOLD
5.31 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
12,378.92 units
benchmarks/server/02_json_api/server📈 view plot
⚠️ NO THRESHOLD
5.42 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
1,002.42 units
🐰 View full continuous benchmarking report in Bencher

cuzzo added a commit that referenced this pull request May 16, 2026
Locals sourced from `x.type_info rescue nil` are provably nil|Type
post-#45, so the `Type.new(ti) if ti && !ti.is_a?(Type)` coercions
are dead and `if ti.is_a?(Type)` is a redundant nil-check. Collapsed:
escape_analysis per_fn_scan!(238), e2_loop_carry_names!(decl_ti,
outer_ti), e3_mark_carry_expr!(904,910); control_flow
_collect_share_moves; promotion_plan stamp_field_pre_cleanups!.
#52's 327/374 are .symbol.type (heterogeneous, = #47), left alone.
Gates green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 16, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchdecomplex
Testbedubuntu-latest

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
Benchmarkleak-build-msMeasure (units) x 1e3leak-countMeasure (units)leak-run-msMeasure (units)
benchmarks/concurrent/01_socket_throughput/bench📈 view plot
⚠️ NO THRESHOLD
2.99 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
1,295.87 units
benchmarks/concurrent/06_dynamic_spawn/bench📈 view plot
⚠️ NO THRESHOLD
1.93 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
3,008.91 units
benchmarks/concurrent/11_parallel_aggregation/bench📈 view plot
⚠️ NO THRESHOLD
1.93 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
6,191.75 units
benchmarks/concurrent/18_atomic_counter/bench📈 view plot
⚠️ NO THRESHOLD
1.86 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
40.80 units
benchmarks/inter-clear/04_concurrent_mvcc_fat_struct/bench📈 view plot
⚠️ NO THRESHOLD
2.07 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
348.46 units
benchmarks/sequential/03_alloc_throughput/bench📈 view plot
⚠️ NO THRESHOLD
1.85 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
11,625.32 units
benchmarks/sequential/13_soa_layout/bench📈 view plot
⚠️ NO THRESHOLD
1.87 units x 1e3📈 view plot
⚠️ NO THRESHOLD
0.00 units📈 view plot
⚠️ NO THRESHOLD
741.76 units
🐰 View full continuous benchmarking report in Bencher

cuzzo and others added 21 commits May 17, 2026 00:25
Evolves triage from "group dark arms by method" to "classify each
dark arm by which testing modality can reach it". Four buckets:

  fuzz_axis        valid program, unseen shape (case-on-AST, &&/||,
                   live if/while body) -> one fuzz axis covers a
                   family + a mutant
  negative_spec    the arm raises/diagnoses -> invalid-program only;
                   fuzz cannot reach it by construction
  ffi_integration  extern/require/module boundary -> needs a real
                   external artifact a fuzzer can't synthesize
  accept_defensive narrow inert residue (synthetic else, empty, nil)
                   -> annotate + accept; human-confirmed, never
                   auto-accepts a reachable arm

Classification is AST-structural, NOT a regex over the arm line
(the rejected fake-value grep): the SimpleCov parent tuple gives the
decision kind, and the arm's (line,col) span is matched to an AST
node whose subtree is inspected for raise/FFI. Two PER-PROJECT
LEXICON constants (FFI boundary methods, diagnostic message names)
are the only project-specific knobs -- the algorithm generalizes,
swap the lexicon per codebase.

Result over the 3 lowering files: fuzz_axis 590, accept_defensive
296, ffi_integration 53, negative_spec 16. This is the work plan:
not one fuzz test, not tons of unit tests, not an integration suite
-- overwhelmingly fuzz axes, a bounded FFI .cht set, a tiny
negative-spec set, a human-confirmed accept residue.

Lives entirely in the coverage tool; decomplex untouched and stays
static/zero-runtime (boundary preserved).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The 6 proposed fuzz_axis matrices, built and run. Result:

BUGS (3 real, all OPEN — deliberately not fixed):
  catch_allocator_matrix surfaces B1: `r = maybe("") OR fbv` (frame
    fallback, heap success) -> Invalid free (invariant #9).
  catch_reassign_matrix surfaces B2 (leak: reassign-through-OR on
    success) and B3 (segfault: struct field fallback reads itself
    mid-cleanup -> UAF).
  All three are the catch/OR-rescue allocator-identity family — the
  exact P0 cluster branch_gap_triage flagged (infer_catch_value_
  allocator 12/12 dark). The modality plan predicted it; the targeted
  matrices confirmed real memory-safety bugs there.
  Documented in docs/agents/fuzz-matrix-surfaced-bugs.md; the failing
  :pass cells are the live tickets.

CLEAN: capability_wrap_matrix (3/3, +3 in_dev), match_matrix (6/6),
  indexed_assignment_matrix (20/20), binary_op_matrix (21/21) — after
  fixing two template-correctness bugs of mine (off-by-one list index;
  inverted string lt/gte oracle). These were my noise, not CLEAR bugs.

COVERAGE: 68 cells moved mir_lowering branch coverage 673 -> 671 (2
  arms). Verified real (COVERAGE=1 fuzz run writes a transpile-tests
  resultset entry with mir_lowering data; branch_gap_triage merges
  it). This reproduces the "92 programs -> 50 arms" result more
  starkly: feature-level fuzzing finds bugs well but is NOT a
  branch-closure lever — the dark arms need exact triggering
  type_info, and/or the fuzz_axis bucket is over-assigned vs
  reachability. Full analysis in the forensic doc.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Feasibility verified: the @target==:bc branches in mir_lowering fire
during MIRLowering#lower_program (Ruby), before the bytecode VM. The
incomplete _bc_runner is irrelevant -- we never execute, never require
BcEmitter to succeed; a program that hits `raise Unimplemented` in a
:bc arm still covered that arm. Per-file rescue, zero new programs.

Result: mir_lowering dark arms 671 -> 656 (15 closed) by re-lowering
the existing corpus with target: :bc. Cost comparison vs the 6
hand-written matrices: 15 arms / 0 new programs vs 2 arms / 68 new
programs (~250x more cost-efficient). But still only 15/671 -- which
is the decisive evidence, from a second direction, that the remaining
~581 fuzz_axis-bucketed arms are NOT closable by program generation in
any backend mode. They are internal-IR-state / defensive guards: the
fuzz_axis bucket is over-assigned and mir_lowering branch closure is a
re-triage problem, not a test-generation problem.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New `bc-lower-coverage` job mirroring the transpile-tests / tools-fuzz
coverage pattern: COVERAGE=1, run tools/bc_lower_coverage.rb, collate,
upload to Codecov with flags `ruby,bc-lower`. Pure Ruby -- no Zig, no
clear build (the @target==:bc arms are covered during MIRLowering,
before the bytecode VM; the incomplete _bc_runner is never executed),
so the job is minimal and fast. fail_ci_if_error: false, matching the
other coverage jobs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rebuild both from SAMPLED axes to EXHAUSTIVE enumeration of the
dispatch's own when-labels, with surface syntax confirmed from
lexer/transpile-tests (not guessed):
  binary_op: every comparison op incl. LTE/GT (was missing), POW
    int+float (** confirmed), MOD, concat, OR. Symbol-path EXCLUDED
    -- CLEAR has no surface symbol literal, so those {EQ,NEQ} arms
    are not source-reachable (accept, not fuzz). 21 -> 30 cells, all
    clean.
  capability_wrap: one cell per ft.sync x ownership label
    {locked, write_locked, always_mutable, versioned, atomic-ptr,
    multiowned, shared:locked} -- all forms confirmed from
    transpile-tests; zero in_dev. 6 pass.

Surfaces B4: @indirect:atomic + WITH EXCLUSIVE (both the compiler's
own directed forms) -> invalid Zig `no field 'ctrl' in AtomicPtr`.
The atomicPtrCreate dark arm of compose_capability_wrap is broken.
OPEN, not fixed.

Coverage delta from the provably-complete enumeration: mir_lowering
656 -> 653 (3 arms). Fourth independent confirmation that branch
coverage is not closable by test generation; documented in the
forensic. Fuzz's value here is bug-finding (4 bugs on dark arms),
not coverage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…riven

The project's primary goal made concrete: not "this decision is
duplicated N times" (scatter) but "THIS contract is the SOURCE of N
defensive type/nil decisions -- fix the contract once, the cluster
dies." Attributes every is_a?/kind_of?/instance_of?/nil?/respond_to?/
safe-nav guard to the canonical root contract of its subject,
resolving proximate locals through INTRA-procedural assignment
(reuses the derived-state def-use idea + semantic-alias-style
canonicalization). Cross-procedure pressure stays nil-kill's by the
recorded boundary -- not re-implemented (decomplex stays CFG-free).

Ranks contracts by decisions x methods; unresolved ~local bucket
sorts last (that residue needs cross-proc = nil-kill). New tier-1
report section. Self-tested: decision_pressure_test (5), full suite
44/124/0.

Verified on src/ (93 files): top contract `.type_info` drives 274
defensive decisions across 94 methods; the type-contract family
(.type_info 274, .value 110, .full_type 33, .type 28, .return_type
27, [:type] 29) dominates the head of the ranking -- exactly the
"one loose contract -> hundreds of conditionals" the user predicted.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Promotes tools/branch_gap_triage from a one-off probe to a
first-class gem (named `prick` -- it pricks holes in your codebase).
A flat "673/2732 uncovered" is unactionable; prick categorizes every
dark branch arm and overlays fix-churn so the actionable slice
surfaces.

OWNS the gap-categorization analysis (AST-structural per-arm
classifier, dead/live decision split, categorical rollup). CONSUMES
the sibling fix-cache gem for churn (require_relative, not
re-derived) + an optional nil-kill verdict for type_norm. Boundary
held: it aggregates, it does not re-implement.

Categories: type_norm (confirm w/ nil-kill -> removable), dead
(delete, complexity down), defensive (accept), ffi, diagnostic
(negative spec), genuine (the real gap). New signal: genuine x
fix-churn = "bugs highly likely HERE".

Validated on src/mir/{mir_lowering,control_flow,escape_analysis}:
935 dark arms -> diagnostic 305, genuine 273, type_norm 229, dead
68, ffi 46, defensive 14. Bugs-likely #1 mir_lowering (187 genuine x
churn 1.0); top sites hoist_alloc / owned_value_temp_needs_cleanup?
-- the exact methods that produced B1-B4. The synthesis points at
real bugs.

Honest v0 caveats (documented in design.md): diagnostic over-greedy
(subtree-wide raise), type_norm under-counted (no intra-proc
local->accessor resolution yet). Shape + bug-likely join are sound;
percentages are candidates to tighten. Self-tested 6/30/0 incl. a
real stdlib-Coverage resultset integration + temp-git churn overlay.
sorbet: ignore gems/prick/.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Addresses three valid critiques:

1. Repo-relative + linked paths. Was absolute
   (/home/yahn/cheat/...); now [src/x.rb:226](src/x.rb#L226).

2. Report leads with the actionable artifact. Dropped the
   unhelpful per-file %-table; the headline is now "Top True Gaps
   (N) -- test these, ranked by fix-churn": every genuine reachable
   arm, linked, sorted by the file's fix-cache churn score. Compact
   category summary follows as context.

3. General gem, no baked-in repo lexicon/jargon. Category action
   text is now testing-strategy-neutral (no .cht / fuzz / nil-kill).
   The FFI/external-boundary lexicon ships EMPTY in the gem and is
   caller-supplied via --ffi (CLEAR's set lives in exe/prick, not
   the library). DIAGNOSTIC_MIDS is general Ruby. The engine
   (categorize uncovered branches, rank genuine by consumed
   fix-cache churn) is general to any Ruby project.

classifier: ffi_boundary injected (kwarg, default []); doc comment
de-jargoned + rename-mangled history ref removed. rollup: emits
top_gaps (genuine arms ranked by churn) instead of file-level
bug_likely. README/design.md rewritten generic + caveats kept.
Tests updated for new signatures/shape; 6/30/0. report.md
regenerated (Top True Gaps headline, linked paths).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
report.md lives at gems/prick/report.md, so `src/x.rb` resolved to
the nonexistent gems/prick/src/x.rb. Report now computes the href
relative to the OUTPUT file's directory (link_base = dirname of
--output; defaults to repo root for stdout). Link is now
../../src/mir/mir_lowering.rb#L226 from gems/prick/report.md; display
text stays the readable repo-relative path. Verified target resolves;
6/30/0.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
gems/prick -> gems/slopcop: directory, lib/, exe/, gemspec, module
Prick -> SlopCop, require paths, CLI name, sorbet ignore entry, and
README/design branding. Tests unchanged (6 runs / 30 assertions / 0
failures); CLI smoke-tested.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
other_type is sig-typed Type and nil-kill confirms the runtime
producer is always Type, so both `other_type.is_a?(Type) &&` guards
are provably dead. Removing them is behavior-preserving (nil-kill
Union Decomplexity: "always Type: collapse, all 2 die").

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ti is sig-typed Type and nil-kill confirms the runtime producer is
always Type, so `ti = Type.new(ti) if ti && !ti.is_a?(Type)` and
`ti = nil unless ti.is_a?(Type)` are dead. Removing them is
behavior-preserving (nil-kill: "always Type: collapse, all 2 die").

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
No CLEAR transpiler bugs encountered. Documents that only nil-kill
"always Type" verdicts are safe standalone guard collapses (#55,#56
done); the other 18 tracked contracts are legitimately nilable or
unattributed -- their is_a?(Type) checks are correct discriminators,
so they need the producer-side propagation typing program, not blind
guard deletion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ull_type=

62 sites set `.full_type = :Sym`, which full_type= silently launders
via Type.new -- the source of the nilable/non-Type pollution that
forces is_a?(Type) re-guards across 38 reader methods. Pass Type.new
at the producer instead (runtime-identical; the setter already did
exactly this). Step 1 of the source-tightening program for #45.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
pipeline_rewriter producer conversion is safe (uniform Locatable
receivers, landed f29524a). The same transform on annotator.rb et
al. regressed 1799 specs: heterogeneous full_type receivers, some of
which genuinely store/read a raw Symbol. The source fix needs
per-receiver typing, not a blanket caller rewrite.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
nil-kill-attributed Locatable#full_type= producer site. Wrap the
Symbol RHS in Type.new (runtime-identical to the setter's existing
launder). Validates the per-site approach for the 22-site producer
worklist nil-kill enumerates for this contract.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The 10 nil-kill-attributed Locatable#full_type= producer sites
(303,307,368,445,503,611,645,911 + the 1623/1686 case exprs) wrapped
in Type.new -- runtime-identical to the setter's launder. Scoped
strictly to nil-kill's worklist (other :Void/:Bool sites untouched).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The 11 nil-kill-attributed Locatable#full_type= producer sites in
annotator.rb wrapped in Type.new (3838 case fixed per-branch, no
double-wrap). Surfaced real slop: visit_Slice declared
`returns(Symbol)` but is a side-effecting annotator whose return is
unused -- only "satisfied" by the pre-launder Symbol. Corrected to
`.void`, matching its sibling visitors (visit_HashLit/_YieldExpr).
Completes nil-kill's 22-site producer worklist for this contract.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
With producers passing Type (prior commits) the Locatable#full_type=
setter guarantees the .type_info contract is strictly nil|Type, so
`x.type_info.is_a?(Type)` is a redundant nil-check. Collapsed to
nil-safe access (&. / truthiness / drop the dead Type.new branch)
across function_analysis(5), escape_analysis(3), generic_analysis(3),
mir_checker(1), mir_lowering(2). The 3 remaining sites
(annotator.rb:2793 final_type; 6522/6618 classify_og_kind param) are
different contracts, intentionally untouched. Completes #45.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
.full_type and .type_info return the same @type_object, so the
producer work in #45 already guarantees this contract is strictly
nil|Type. All 14 .full_type is_a?(Type) reader guards collapsed to
nil-safe access (&. / truthiness; dead Type.new branches dropped,
incl. the 5014 block guarded by an outer non-nil check). Gates green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Locals sourced from `x.type_info rescue nil` are provably nil|Type
post-#45, so the `Type.new(ti) if ti && !ti.is_a?(Type)` coercions
are dead and `if ti.is_a?(Type)` is a redundant nil-check. Collapsed:
escape_analysis per_fn_scan!(238), e2_loop_carry_names!(decl_ti,
outer_ti), e3_mark_carry_expr!(904,910); control_flow
_collect_share_moves; promotion_plan stamp_field_pre_cleanups!.
#52's 327/374 are .symbol.type (heterogeneous, = #47), left alone.
Gates green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
cuzzo and others added 16 commits May 17, 2026 21:10
…egression gate

@objects (object_id -> owners, the collection mutation-routing table)
was a plain Hash, NEVER evicted, never serialized. Every transient
collection leaked an entry forever (50,001 in a 50k probe); GC then
marked a monotonically growing live graph each cycle. A real
unbounded-memory bug (OOM risk on long real collects).

ObjectSpace::WeakMap is unusable here: verified on this env Ruby 3.2.3
holds WeakMap VALUES weakly, so the owners hash would vanish for LIVE
collections -> lost mutation attribution (output regression). Instead
evict via ObjectSpace.define_finalizer when the collection is GC'd: a
GC'd collection can never be mutated again, so NO recorded mutation is
ever lost and downstream output is byte-identical. The finalizer is
built in a separate factory so its closure captures only the Integer
object_id + the module, never `value` (capturing value would pin it
alive forever -> finalizer never runs -> leak reinstated). Fully
localized to register_collection_owner; the 13 mutation-hook read
sites + record_collection_mutation keep object_id keys UNCHANGED.

Verified:
- new in-process regression gate (runtime_trace_spec.rb): live
  collection stays linked (mutation still attributable); 20k transient
  registrations -> @objects bounded < 1000 (was 20_001); live entry
  survives eviction.
- full nil-kill suite 339/1 -> 340/1 (gate added & passing; only the
  documented :2168 Feature-A item still open; zero regressions;
  byte-identical recorded output).
- collect micro-bench (N=200000): ~40,400ms -> 36,351ms; cumulative
  with prior perf WIP 50,732 -> 36,351 (~1.4x). HONEST: NOT 5-10x.
  The leak was a ~4s slice at this scale; its primary value is
  correctness/memory at real scale. Residual ~36s is the per-call
  instrumentation structure (wrapper Object.new + catch/throw +
  intrinsic recorder work + transient-alloc GC), not a leak/memoizable
  recompute -> 5-10x would need an instrumenter-wrapper change or
  sampling.

Open items unchanged: :2168 (Feature A ignores enclosing sig-typed
param receiver); instrumenter-wrapper per-call cost (the real residual
ceiling).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
SimpleCov add_filter "/tools/" drops it from every worker resultset
before collate_coverage merges into coverage.xml; codecov.yml
ignore: ["tools/"] is the server-side safety net. tools/ is dev
scripting, not production src/ -- it was diluting the %.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
FunctionDef/LambdaLit/ExternFnDecl#initialize+params= and
FunctionSignature already coerce params to a non-nil Array
([].map{Param.coerce}). Functions take 0 params, never nil. Every
(x.params || []) / x.params&. across annotator/mir_lowering/
promotion_plan/reentrance/effects/thunk/type/scope was dead cruft.
Deleted the decision (not relocated it). Trinity green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Delete the last AST coerce() bridges (Capability, MatchCase,
PatternField) so every producer builds the struct directly -- no
more anonymous-Hash + struct + coerce dual representation. Parser
emits AST::Capability/MatchCase/PatternField; the collection seams
(WithBlock#capabilities, MatchStatement#cases, StructPattern#fields,
IfBind#bindings) drop their per-element coerce maps.

Harden always-present fields to non-nil (the contract: a collection
is empty, never nil): FunctionDef#params=, MatchCase#body/extra_values,
IfBind#bindings=, WithBlock#capabilities=, StructPattern#fields=,
MatchStatement#cases=, Binding#unwrapped_type=, Capability#resolved_type=,
PatternField#name_token. extra_values now defaults to [] in
initialize; match_multi_arm specs assert eq([]) instead of be_nil.

Net: T.untyped 1660->1654 (below baseline), T.nilable 843->829;
the residual nilable is honest grammar-optionality (binding,
destructure, guard_expr, etc.).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
parse_argument_list was shared by FN-param and USE-capture parsing,
emitting a Hash that Param.coerce normalized at the FunctionDef /
FunctionSignature seam -- the last Hash+struct+coerce dual system.

Split it: param path builds AST::Param directly (as_param: true,
default); capture path keeps its Hash (distinct downstream shape,
unchanged). Convert every remaining param-Hash producer to
AST::Param.new -- method-stub parse, FN-type annotation parse,
build_lambda_signature, intrinsic arg_spec, visit_ExternFnDecl,
pre_register_function (x2). Delete Param.coerce; the FunctionDef /
LambdaLit / FunctionSignature seams now assign directly. Strengthen
FunctionSignature#initialize params sig T::Array[T.untyped] ->
T::Array[AST::Param]. Spec fixtures/helpers building param Hashes
migrated to AST::Param.new.

No Hash representation of a parameter exists anywhere now. T.untyped
1660->1653; is_a?(Type) 281->225.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
None of these could ever be nil; the annotations were wrong:

- MatchCase#indirect_payload_as: only ever set `= true`, read only
  as truthy. Default false; reader/setter now T::Boolean (-2).
- parse_argument_list: parse_comma_seq always returns [tok, items]
  with items an Array, never nil. Return now T::Array (-1).
- full_type_or: every caller passes a default or block; the nil
  path was dead. Return the :Untyped Type (consistent with
  full_type's non-nil contract) instead of nil. Return now
  T.any(Type, Symbol, String) (-1).

T.nilable 829->825.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@borrowed_alias, @mutable_ref_target, @poly_borrow_target, @MuTaTeD,
@READ, @takes, @is_param were T.let(nil, T.nilable(T::Boolean)) but
are only ever written `= true` and read as truthy -- nil was a
phantom third state never distinguished from false (zero `.nil?` /
`== nil` reads anywhere). Default false; type T::Boolean.

T.nilable 825->818 -- below the 820 baseline.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
All expression full_type readers returned Type.new(...) at runtime but
only 1 of 5 carried a sig -- so Sorbet couldn't prove non-nil and
downstream defended against a nil that cannot occur. Sign the other 4
(StatementVoidType, BinaryOp, UnaryOp, Literal) sig { returns(Type) },
adding extend T::Sig where the struct lacked it.

Remove now-provably-phantom operand guards: BinaryOp#full_type's
left&. / right&. and UnaryOp#full_type's right&. -- a BinaryOp always
has both operands and a UnaryOp always has its operand (the very next
.resolved call would already NoMethodError if they were nil, proving
the guards dead).

Trinity green. Contract is now type-enforced, not just runtime-true.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add typed accessors for the last two untyped members (symbol ->
T.nilable(SymbolEntry), capture -> T.nilable(String)); migrate the
sole genuine Binding hash-access site (annotator.rb:1347 b[:name] ->
b.name). All 6 members now strongly typed, no [:key] backdoor.

(mir_emitter b[:expr]/b[:capture] is MIR::IfBindStmt's separate
binding hash, not AST::Binding -- out of scope.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The CATCH clause was a 3-level untyped Hash nest ({items:[{form,
name,token}], filters:[{form,value,token}], body:} mutated by the
annotator to add kinds/types/filter_types/filter_messages). Replace
with AST::CatchClause + AST::CatchItem + AST::CatchFilter, every
member strongly typed; annotator-stamped arrays default [] (never
nil). Migrate the single producer (parser parse_catch_item /
parse_catch_filter / catch_clauses<<) and the genuine consumers
(resolve_catch_clause!, annotator 793/847, mir_lowering catch-meta /
build_catch_clauses / 1643, mir_pass 200) from [:key] to .key.

Receiver-verified per site: SyncPolicyDecl handlers, error-selector
clauses, and thunk descriptors also use `clause`/`c` but are NOT
CatchClause -- left untouched. Zero hash access on the new structs.

Trinity green (4773/0, 554/554, fuzz 141/141).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
node.captures elements were the loose parse_argument_list(as_param:
false) Hash {name,type,default,mutable,takes,comptime,name_token},
mutated by verify_captures! (cap[:type]=, cap[:storage]=) and read by
declare_captures. Replace with AST::Capture: every member strongly
typed; mutable/takes/comptime normalized to Bool (match! yields a
Token-or-falsy); type normalized to Type; storage stamped via setter.
Migrate the sole producer (parser as_param:false branch) and the two
genuine consumers (verify_captures!, declare_captures) [:key]->.key.

Receiver-verified: mir_checker structure.captures (FSM record, has
cleanup_at) and bg_capture_classifier a.captures (Hash{name=>type})
are DIFFERENT records -- left untouched.

Trinity green (4773/0, 554/554, fuzz 141/141).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
StructDef#fields / ExternStructDecl#fields were a loose
{type,default,borrowed} Hash keyed by name. Replace the value with
typed AST::StructField; rename the member to field_decls (kills the
.fields-means-4-things ambiguity for the declaration form). Both
StructDef and ExternStructDecl share StructField via the common
parse_struct_body producer.

Migrate every type-verified consumer to .type/.default/.borrowed:
visit_StructDef, visit_ExternStructDecl, lower_struct_def,
lower_extern_struct, compiler_frontend/importer schema registration,
promotion_plan + type.rb copy/escape checks, atomic suggester.

Root-caused the prior revert: the defensive `field_def.is_a?(Hash)`
branches (mir_lowering:5410 borrowed-field lowering, annotator
1399/1409/1715, type.rb 1472/1727) silently fell through for a
non-Hash StructField -- the borrowed list field lowered as an owned
ArrayList instead of a []T slice (185_borrowed_iterator). All six
migrated to is_a?(AST::StructField) with .type/.borrowed.

Trinity green (4773/0, 554/554 incl. 185_borrowed_iterator, fuzz
141/141).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The collection constructor previously only parsed strictly-empty
brackets (`consume('['); consume(']')`). Parse a comma-separated
expression list instead (reusing the array-literal parse_comma_seq),
so `List[1, 2, 3]` / `Pool[a, b]` / `Set[x]` are element-initialized
and `List[]` remains the empty form. ListLit carries the parsed
items instead of always [].

Trinity green (4773/0, 554/554, fuzz 141/141).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ap sigil

Single representation throughout the compiler — no Hash/typed dual paths:

- Schemas::{Struct,Union,Enum,Resource}Schema + InlineStructVariant are
  the only schema representations. The annotator constructs them at
  scope.declare_type; ~150 consumer sites use typed accessors and the
  Schemas.{struct,union,enum,resource,inline_struct,field_bearing}?
  predicates. Deleted the as_*_schema Hash<->typed bridge and every
  `schema.is_a?(Hash) && schema[:kind]==...` branch.
- StructSchema#fields is uniformly {String => AST::StructField};
  field_defaults/borrowed_fields are derived, not parallel state.
- MIR lowering READs annotator stamps (StructLit#borrowed_field_names,
  needs_heap_create) instead of re-resolving schemas.
- @indirect is one pointer level for every type (Type#compute_zig_type),
  killing the indirect_fields/needs_heap_create side-channel. Added
  INV-INDIRECT-SINGLE-BOX MIR-checker invariant + fuzz template.
- Removed `%` as a heap type sigil (lambda %(...) kept); migrated all
  usage to `@indirect`; return-type @indirect treated as a storage
  directive; struct-pointee @indirect keeps move/cleanup parity.

Gate: prspec 4773/0, transpile-tests 555/555 (0 leaks), fuzz 153/153.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@cuzzo cuzzo changed the title branch_gap_triage: AST-structural modality bucketing Rename slop gems, improve their metrics, and consume them to reduce complexity May 18, 2026
- sorbet/rbi/clear-attr-accessors.rbi: regenerated for the schema
  strong-typing attrs (StructLit#borrowed_field_names,
  GetField#indirect_field, Schemas::InlineStructVariant, StructSchema
  derived methods, StructField#borrowed now T::Boolean, etc.).
- .rubocop_todo.yml: add intrinsic_registry.rb and pre_mir_type_check.rb
  to the Sorbet/EnforceSignatures Exclude. Both are wholly-unsigned
  `# typed: false` EPIC-65 in-progress files that exist only on this
  branch; the todo was auto-generated from master before they landed.
  Same allowlist treatment as the other 69 not-yet-signed files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@cuzzo cuzzo changed the title Rename slop gems, improve their metrics, and consume them to reduce complexity Rename slop gems, improve their metrics, and consume them to reduce complexity. Also, remove legacy %T. May 18, 2026
@cuzzo cuzzo changed the title Rename slop gems, improve their metrics, and consume them to reduce complexity. Also, remove legacy %T. Project Un-Slop pt 1: Rename slop gems, improve their metrics, and consume them to reduce complexity. Also, remove legacy %T. May 18, 2026
cuzzo and others added 9 commits May 18, 2026 18:50
The schema strong-typing work added kind/struct?/union?/enum?/
resource?/inline_struct? predicates, field_defaults/borrowed_fields
derivations and InlineStructVariant ==/hash to schemas.rb, which is
`# typed: strict` — every `def` needs a sig. Adds them (24 srb 7017
errors -> 0).

Also: sorbet/config ignores .claude/ so agent isolation worktrees
don't get double-type-checked (every Struct constant redefined).
CI-irrelevant (fresh checkout) but removes local noise.

Branch Sorbet baseline was already red from the in-progress EPIC-65
strict-typing migration (155 errors at 182c269, pre-dating this
work, in ast.rb/pipeline_host/ownership_graph/etc.). This change
takes it 155 -> 130; it does not make Sorbet green — the remaining
130 are that separate epic's debt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The branch's typed-struct / full_type / schema migration introduced
130 Sorbet errors (master is clean). All resolved, surgically:

- 7015/7050 redundant T.must: stripped (Type/full_type are non-nil
  post the full_type->non-nil arc) — pure no-op, prspec-neutral.
- 7017 missing sigs: added sigs to the 28 branch-added coercion
  methods in ast.rb/mir.rb/ownership_graph.rb; added `extend T::Sig`
  to the Struct/module blocks that lacked it.
- schemas.rb predicates/derivations already signed earlier.
- 5005 ivars: declare @return_type/@type via T.let in initialize;
  setters do plain assignment.
- 7027/6002/7043 (ast.rb): T.let the LITERAL_VALUE_TYPE const and the
  StatementVoidType @type_object memo.
- 7002/7005: Capability/Binding typed-struct sigs (lock_helper
  expanded_capabilities, parser parse_if_bind_body/extract_paren_
  bindings, FunctionSignature#zig_pattern widened to String|Symbol).
- 7003 nil-safety: post-error `next`/`return` so Sorbet narrows;
  T.must where a static key guarantees presence; T.unsafe at three
  IntrinsicRegistry.sig(reg, node.X) sites Sorbet mis-resolves.
- sorbet/config: ignore gems/boobytrap/ (branch-added tool gem, like
  the other ignored gems) and .claude/ (agent worktrees).
- clear-attr-accessors.rbi regenerated.

Gate: srb tc 0, prspec 4773/0, transpile-tests 555/555 (0 leaks),
fuzz 153/153, rubocop EnforceSignatures clean, RBI fresh.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…a free

- std_lib: mark .first()/.last() lifetime:self so ?String results borrow
  the container instead of getting a spurious cleanup (371_first_last
  invalid free of ?[]const u8).
- escape_analysis: Condition 9 now stamps the declaration's full_type
  provenance to :heap (not just symbol storage), so a String[]@list
  mutated through a MUTABLE param deinits with heapAlloc instead of
  frameAlloc -- fixes the fuzz mutable_collection_param string-elem
  bundle leak.
- promotion_plan: classify_optional skips cleanup for rodata/borrow
  provenance; an ?String literal is rodata and cleanupAlloc only skips
  frame, so freeing it was an invalid free (168_test_predicates).
- loop_frame spec: assert the move-guarded defer form (loop-carry
  reassignment makes resp move-tracked) -- matches current correct
  codegen.
- Sorbet: declare @guarded_cleanup_names with T.let, narrow
  walk_consumed object access to AST::MethodCall.

Plus in-progress FSM transform / mir WIP. All CI suites verified green:
prspec, transpile-tests, fuzz matrix, sorbet, rubocop, nil-kill,
module/ffi integration, bc-lower.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Resolved conflicts (87 master commits, 104 ours; merge over rebase to
avoid replaying 104 commits / force-pushing the shared branch):

- .github/workflows/ci.yml: kept BOTH the bc-lower-coverage job (ours)
  and the disabled register-vm-allowlist job (master).
- src/mir/control_flow.rb: MatchStatement scan uses typed c.body (our
  typed-struct refactor) + master's 'if s.default_case' nil guard.
- src/backends/pipeline_rewriter.rb (9 hunks): kept our typed
  full_type = Type.new(...) + master's IfStatement else_branch [] (not
  nil) from 8316365 'restore IfStatement else_branch invariant'.

Semantic fix from the merge:
- examples/minivm/register_bc_emitter.rb: master's register-VM code
  used the old Hash API stmt.stdlib_def[:borrows]/[:fallible_clauses];
  our branch made stdlib_def a typed FunctionSignature. Switched to
  stdlib_def&.emit&.borrows / .fallible_clauses (canonical typed path).

Verified green: prspec (4819, 0 fail), srb tc, rubocop signatures,
attr RBI, ./clear test transpile-tests/ (0 leaks), fuzz matrix
(145 ok), register_shared_cell_spec (7/7).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
decomplex refactored stdlib_def from a Hash into a FunctionSignature
(ownership-effect keys moved onto emit), but bc_emitter still did
stdlib_def[:tag]/[:borrows]/[:elem]/[:fallible_clauses], raising
NoMethodError: undefined method `[]' for FunctionSignature.

Add sd_get(sd, key) normalizing access across both forms (legacy Hash
literal from mir_lowering, or FunctionSignature whose emit struct carries
the key) and route all 7 read sites through it. Fixes 16 integration
specs (10 stack-snapshot + 6 vm.cht-binary).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@cuzzo cuzzo merged commit 9c9edd4 into master May 19, 2026
39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants